package com.qingzhuge.netty.server;

import com.qingzhuge.netty.common.autoconfigure.NettyServerProperties;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.ImmediateEventExecutor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.net.InetSocketAddress;

/**
 * @author : zero.xiao
 * @description :
 * @date :2018/9/28 0028 下午 14:49
 * @modified :
 * @since : 1.0.0
 */
@Slf4j
public class NettyServer {

    @Autowired
    private NettyServerProperties properties;

    private ChannelGroup channelGroup = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);
    /**
     * boss线程
     */
    private EventLoopGroup bossGroup = new NioEventLoopGroup();
    /**
     * work线程，通过nio方式来接收连接和处理连接
     */
    private EventLoopGroup workGroup = new NioEventLoopGroup();

    private Channel channel;

    public void start() {
        ServerBootstrap bootstrap = getServerBootstrap();
        ChannelFuture future;
        String host = properties.getHost();
        int port = properties.getPort();
        if (StringUtils.isEmpty(host)){
            future = bootstrap.bind(port);
            log.debug("Netty server start >>> 0.0.0.0:{}",port);
        }else {
            // 服务器绑定IP/端口监听
            InetSocketAddress address = new InetSocketAddress(host, port);
            future = bootstrap.bind(address);
            log.debug("Netty server start >>> {}:{}",host,port);
        }
        future.syncUninterruptibly();
        channel = future.channel();
        Runtime.getRuntime().addShutdownHook(new Thread(this::destroy));
        future.channel().closeFuture().syncUninterruptibly();
    }

    private ServerBootstrap getServerBootstrap() {
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workGroup)
                .channel(NioServerSocketChannel.class)
                // 设置过滤器
                .childHandler(new NettyServerInitializer(properties))
                /*
                * ChannelOption.SO_BACKLOG对应的是tcp/ip协议listen函数中的backlog参数，
                * 函数listen(int socketfd,int backlog)用来初始化服务端可连接队列，服务端处理客户端连接请求是顺序处理的，
                * 所以同一时间只能处理一个客户端连接，多个客户端来的时候，服务端将不能处理的客户端连接请求放在队列中等待处理，
                * backlog参数指定了队列的大小
                *
                * BACKLOG用于构造服务端套接字ServerSocket对象，
                * 标识当服务器请求处理线程全满时，用于临时存放已完成三次握手的请求的队列的最大长度。
                * 如果未设置或所设置的值小于1，Java将使用默认值50。
                * */
                .option(ChannelOption.SO_BACKLOG, 128)
                /*
                * ChanneOption.SO_REUSEADDR对应于套接字选项中的SO_REUSEADDR，这个参数表示允许重复使用本地地址和端口，
                * 比如，某个服务器进程占用了TCP的80端口进行监听，此时再次监听该端口就会返回错误，使用该参数就可以解决问题，
                * 该参数允许共用该端口，这个在服务器程序中比较常使用，
                * 比如某个进程非正常退出，该程序占用的端口可能要被占用一段时间才能允许其他进程使用，
                * 而且程序死掉以后，内核一需要一定的时间才能够释放此端口，不设置SO_REUSEADDR就无法正常使用该端口。
                * */
                .option(ChannelOption.SO_REUSEADDR, true)
                /*
                * 针对子线程的配置，保持长连接
                * Channeloption.SO_KEEPALIVE参数对应于套接字选项中的SO_KEEPALIVE，该参数用于设置TCP连接，
                * 当设置该选项以后，连接会测试链接的状态，这个选项用于可能长时间没有数据交流的连接。
                * 当设置该选项以后，如果在两小时内没有数据的通信时，TCP会自动发送一个活动探测数据报文。
                * */
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                /*
                * 操作接收缓冲区的大小，接收缓冲区用于保存网络协议站内收到的数据，直到应用程序读取成功
                * ChannelOption.SO_RCVBUF 参数对应于套接字选项中的 SO_RCVBUF
                * */
                .childOption(ChannelOption.SO_RCVBUF, 2048)
                /*
                * 操作发送缓冲区的大小，发送缓冲区用于保存发送数据，直到发送成功
                * ChannelOption.SO_RCVBUF 参数对应于套接字选项中的 SO_RCVBUF
                * */
                .childOption(ChannelOption.SO_SNDBUF, 2048)
        ;
        return bootstrap;
    }

    private void destroy() {
        if (channel != null) {
            channel.close();
        }
        channelGroup.close();
        workGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
    }
}
